/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.harmony.javax.naming;

import java.util.HashMap;
import java.util.Hashtable;

import org.apache.harmony.javax.naming.spi.NamingManager;
import org.apache.harmony.jndi.internal.EnvironmentReader;
import org.apache.harmony.jndi.internal.UrlParser;
import org.apache.harmony.jndi.internal.nls.Messages;

/**
 * An <code>InitialContext</code> object is required as the starting context for
 * any naming operations. Other contexts and subcontexts may be created later.
 * Contexts may consist of different implementations according to the needs of
 * the application. All naming operations are performed relative to a context
 * and names are resolved beginning with the initial context.
 * <p>
 * When constructing an initial context, environment properties from a range of
 * sources may be used to initialize the environment. See the specification of
 * the {@link Context} interface for further details of environment properties.
 * </p>
 * <p>
 * The environment at runtime determines the initial context implementation. By
 * default, the naming frameworks look for the initial context factory class
 * name in the property <code>Context.INITIAL_CONTEXT_FACTORY</code>. When URL
 * strings must be resolved, a different policy is used which is described
 * below.
 * </p>
 * <p>
 * A <code>NoInitialContextException</code> is thrown when it cannot create an
 * initial context. The exception may occur not only during constructor
 * invocation, but may occur later. For example, when a subclass of <code>
 * InitialContext</code> uses the lazy initialization option, <code>
 * InitialContext</code> methods may be invoked later which require the
 * initialization to be completed at that time using the <code>init</code>
 * protected method. In these circumstances, <code>NoInitialContextException
 * </code> may be thrown some time after the constructor was invoked. JNDI
 * applications should be written to be independent of when initial context is
 * actually initialized.
 * </p>
 * <p>
 * If environment property <code>Context.INITIAL_CONTEXT_FACTORY</code> has a
 * non-null value, then the specified initial context factory may experience a
 * problem trying to instantiate an initial context and so throw an exception.
 * It is a responsibility of the service provider implementation as to when an
 * exception is thrown to report the problem to the JNDI application.
 * </p>
 * <p>
 * URL names comprising a String format described by RFC1738 may be components
 * of names passed to naming operations. Typically, the URL is composed of the
 * "scheme" - such as one of http, ldap, dns - followed by additional text. If
 * the JNDI can identify the URL scheme from the specified name, then it is used
 * to construct a classname suffix in the following form:<br>
 * 
 * <pre>
 *            &lt;package_prefix&gt; . &lt;scheme&gt; . &lt;scheme&gt;URLContextFactory
 * </pre>
 * 
 * Several variants of the classname are constructed using each element of the
 * <code>Context.URL_PACKAGE_PREFIXES</code> environment property. Note that an
 * additional package prefix - "com.sun.jndi.url" - is always considered to be
 * at the end of those already present in the value of that environment
 * property. Although a service provider may also provide a URL context
 * implementation as well as a context implementation, it is not required to do
 * so, and so an arbitrary service provider might not provide for creating URL
 * contexts.
 * </p>
 * <p>
 * If a URL context is successfully created for a specified URL scheme, the
 * factory can create contexts for arbitrary URLs of the same scheme.
 * <code>NamingManager.setInitialContextFactoryBuilder</code> may be used to
 * specify an alternate policy for locating factories for initial contexts and
 * URL contexts.
 * </p>
 * <p>
 * On successful completion of <code>InitialContext</code> initialization, the
 * service provider implementation will have returned an appropriate <code>
 * Context</code> object which can be used for looking up and manipulating names
 * which may or may not be URL names. <code>InitialContext</code> methods other
 * than those dealing with environments should delegate context operations to
 * that <code>Context</code> object.
 * </p>
 * 
 * @see Context
 */
public class InitialContext implements Context {

	/**
	 * A shortcut method for retrieving the named object by <code>Name</code>.
	 * It is equivalent to
	 * 
	 * <pre>
	 * InitialContext icxt = new InitialContext();
	 * T obj = icxt.lookup();
	 * </pre>
	 * 
	 * <p>
	 * Returns a new instance of this context when <code>name</code> is empty.
	 * The new instance represents the same naming context as this context, but
	 * may be accessed/modified independently and concurrently.
	 * </p>
	 * 
	 * @param name
	 *            the name to be looked up
	 * @return the object bound to <code>name</code>
	 * @throws NamingException
	 *             if a naming exception is encountered
	 * 
	 * @see #doLookup(String)
	 * @see #lookup(Name)
	 * @since 1.6
	 */
	public static <T> T doLookup(Name name) throws NamingException {
		return (T) new InitialContext().lookup(name);
	}

	/**
	 * A static method that retrieves the named object by <code>String</code>.
	 * 
	 * @param name
	 *            the name of the object being looked up
	 * @return the object bound to <code>name</code>
	 * @throws NamingException
	 *             if a naming exception is encountered
	 * 
	 * @see #doLookup(Name)
	 * @since 1.6
	 */
	public static <T> T doLookup(String name) throws NamingException {
		return (T) new InitialContext().lookup(name);
	}

	/**
	 * Set to the result of the first successful invocation of <code>
	 * NamingManager.getInitialContext</code> by <code>getDefaultInitCtx
	 * </code>. Initially null.
	 */
	protected Context defaultInitCtx;

	/**
	 * Set to true when <code>NamingManager.getInitialContext</code> has been
	 * invoked to obtain an initial context. Initially false.
	 */
	protected boolean gotDefault;

	/**
	 * Contains all those JNDI environment properties that were found in any of
	 * the the sources of JNDI environment properties. Initially null.
	 */
	protected Hashtable<Object, Object> myProps;

	/**
	 * Contains loaded properties for each classloader
	 */
	private static Hashtable<ClassLoader, Hashtable<Object, Object>> propsCache = new Hashtable<ClassLoader, Hashtable<Object, Object>>();

	/**
	 * Contians properties load from java.home/lib/jndi.properties
	 */
	private static Hashtable<Object, Object> libProperties = null;

	private final HashMap<String, Context> contextCache = new HashMap<String, Context>();

	/**
	 * Constructs an <code>InitialContext</code> instance without using any
	 * environment properties. This constructor is effectively the same as using
	 * constructor <code>InitialContext((Hashtable)null)</code>.
	 * 
	 * @throws NamingException
	 *             If failed to create an <code>InitialContext</code>.
	 */
	public InitialContext() throws NamingException {
		this(null);
	}

	/**
	 * Constructs an <code>InitialContext</code> instance by indicating whether
	 * a lazy initialization is desired. Effectively, this is the same as using
	 * constructor <code>InitialContext()
	 * </code> if lazy initialization is not indicated.
	 * <p>
	 * This constructor may be invoked with a parameter value of true and the
	 * implementation will defer initialization of the instance. This may be
	 * used in an <code>InitialContext</code> subclass constructor in which
	 * later action will set up a <code>Hashtable</code> object with appropriate
	 * environment properties and pass that to the <code>init</code> method to
	 * complete initialization of the <code>InitialContext</code> object.
	 * </p>
	 * 
	 * @param doNotInit
	 *            Specifies whether to initialize the new instance.
	 * @throws NamingException
	 *             If failed to create an <code>InitialContext</code>.
	 */
	protected InitialContext(boolean doNotInit) throws NamingException {
		if (!doNotInit) {
			internalInit(null);
		}
	}

	/**
	 * Constructs an <code>InitialContext</code> instance using environment
	 * properties in the supplied parameter which may be null.
	 * 
	 * @param environment
	 *            the JNDI environment properties used to create the context
	 * @throws NamingException
	 *             If failed to create an <code>InitialContext</code>.
	 */
	public InitialContext(Hashtable<?, ?> environment) throws NamingException {
		internalInit(environment);
	}

	@Override
	public Object addToEnvironment(String propName, Object propVal)
			throws NamingException {
		synchronized (contextCache) {
			myProps.put(propName, propVal);
			contextCache.clear();
		}
		return getDefaultInitCtx().addToEnvironment(propName, propVal);
	}

	@Override
	public void bind(Name name, Object obj) throws NamingException {
		getURLOrDefaultInitCtx(name).bind(name, obj);
	}

	@Override
	public void bind(String name, Object obj) throws NamingException {
		getURLOrDefaultInitCtx(name).bind(name, obj);
	}

	@Override
	public void close() throws NamingException {
		if (gotDefault) {
			getDefaultInitCtx().close();
		}
	}

	/**
	 * Combines two names into a composite name according to the syntax for this
	 * context. The name <code>prefix</code> is expected to be the name of one
	 * or more of the immediate parent contexts of this context, so should be an
	 * empty name for an <code>InitialContext</code>. <code>name</code> is a
	 * name relative to this context. Neither <code>prefix</code> nor
	 * <code>name</code> may be null.
	 * 
	 * @param name
	 *            a <code>Name</code>, may not be null
	 * @param prefix
	 *            a <code>Name</code> serves as prefix, may not be null
	 * @return the combined name
	 * @throws NamingException
	 *             if an error occurs.
	 */
	@Override
	public Name composeName(Name name, Name prefix) throws NamingException {
		if (null == name) {
			throw new NullPointerException();
		}
		return (Name) name.clone();
	}

	/**
	 * Combines two names into a composite name according to the syntax for this
	 * context. The name <code>prefix</code> is expected to be the name of one
	 * or more of the immediate parent contexts of this context, so should be an
	 * empty string for an <code>InitialContext</code>. <code>name</code> is a
	 * name relative to this context.
	 * 
	 * @param name
	 *            a <code>Name</code>, may not be null
	 * @param prefix
	 *            a <code>Name</code> serves as prefix, may not be null
	 * @return the combined name
	 * @throws NamingException
	 *             if an error occurs.
	 */
	@Override
	public String composeName(String name, String prefix)
			throws NamingException {
		return name;
	}

	@Override
	public Context createSubcontext(Name name) throws NamingException {
		return getURLOrDefaultInitCtx(name).createSubcontext(name);
	}

	@Override
	public Context createSubcontext(String name) throws NamingException {
		return getURLOrDefaultInitCtx(name).createSubcontext(name);
	}

	@Override
	public void destroySubcontext(Name name) throws NamingException {
		getURLOrDefaultInitCtx(name).destroySubcontext(name);
	}

	@Override
	public void destroySubcontext(String name) throws NamingException {
		getURLOrDefaultInitCtx(name).destroySubcontext(name);
	}

	/**
	 * Gets the default underlying <code>Context</code> implementation. If
	 * <code>gotDefault</code> is true, returns the value of <code>
	 * defaultInitCtx</code>. Otherwise, calls
	 * <code>NamingManager.getInitialContext
	 * </code> to return an initial context for the current environment into
	 * <code>defaultInitCtx</code>, then <code>gotDefault</code> is set true. If
	 * the resulting context object is null, a <code>NoInitialContextException
	 * </code> is thrown, otherwise the value of <code>defaultInitCtx</code> is
	 * returned.
	 * 
	 * @return the default context
	 * @throws NoInitialContextException
	 *             If <code>NamingManager.getInitialContext</code> returns null.
	 * @throws NamingException
	 *             If failed to create the default context.
	 */
	protected Context getDefaultInitCtx() throws NamingException {
		initializeDefaultInitCtx();
		return defaultInitCtx;
	}

	@Override
	public Hashtable<?, ?> getEnvironment() throws NamingException {
		return getDefaultInitCtx().getEnvironment();
	}

	@Override
	public String getNameInNamespace() throws NamingException {
		return getDefaultInitCtx().getNameInNamespace();
	}

	@Override
	public NameParser getNameParser(Name name) throws NamingException {
		return getURLOrDefaultInitCtx(name).getNameParser(name);
	}

	@Override
	public NameParser getNameParser(String name) throws NamingException {
		return getURLOrDefaultInitCtx(name).getNameParser(name);
	}

	/**
	 * Returns a non-null context for the specified name of Name representation.
	 * <p>
	 * If an initial context factory builder has been defined, then the
	 * specified <code>Name</code> parameter is ignored and the result of <code>
	 * getDefaultInitCtx</code> is returned. Otherwise, if the first component
	 * of the name is not a URL string, then it returns the result of invoking
	 * <code>getDefaultInitCtx</code>. Otherwise, it attempts to return a URL
	 * context
	 * {@link org.apache.harmony.javax.naming.spi.NamingManager#getURLContext(String, Hashtable)}
	 * , but if unsuccessful, returns the result of invoking
	 * <code>getDefaultInitCtx</code>.
	 * </p>
	 * 
	 * @param name
	 *            a name used in a naming operation which may not be null
	 * @return a context which may be a URL context
	 * @throws NamingException
	 *             If failed to get the desired context.
	 */
	protected Context getURLOrDefaultInitCtx(Name name) throws NamingException {
		// If the name has components
		if (0 < name.size()) {
			return getURLOrDefaultInitCtx(name.get(0));
		}
		return getDefaultInitCtx();
	}

	/**
	 * Returns a non-null context for the specified name of string
	 * representation.
	 * <p>
	 * If an initial context factory builder has been defined, then the
	 * specified name parameter is ignored and the result of <code>
	 * getDefaultInitCtx</code> is returned. Otherwise, if the name is not a URL
	 * string, then it returns the result of invoking <code>getDefaultInitCtx
	 * </code>. Otherwise, it attempts to return a URL context
	 * {@link org.apache.harmony.javax.naming.spi.NamingManager#getURLContext(String, Hashtable)}
	 * , but if unsuccessful, returns the result of invoking <code>
	 * getDefaultInitCtx</code>.
	 * </p>
	 * 
	 * @param name
	 *            a name used in a naming operation which may not be null
	 * @return a context which may be a URL context
	 * @throws NamingException
	 *             If failed to get the desired context.
	 */
	protected Context getURLOrDefaultInitCtx(String name)
			throws NamingException {

		/*
		 * If an initial context factory builder has been defined, then the
		 * specified name parameter is ignored and the result of
		 * getDefaultInitCtx() is returned.
		 */
		if (NamingManager.hasInitialContextFactoryBuilder()) {
			return getDefaultInitCtx();
		}

		if (null == name) {
			// jndi.00=name must not be null
			throw new NullPointerException(Messages.getString("jndi.00")); //$NON-NLS-1$
		}

		// If the name has components
		final String scheme = UrlParser.getScheme(name);
		Context ctx = null;
		if (null != scheme) {
			synchronized (contextCache) {
				if (contextCache.containsKey(scheme)) {
					return contextCache.get(scheme);
				}

				// So the first component is a valid URL
				ctx = NamingManager.getURLContext(scheme, myProps);
				if (null == ctx) {
					ctx = getDefaultInitCtx();
				}
				contextCache.put(scheme, ctx);
			}
			return ctx;
		}
		return getDefaultInitCtx();
	}

	/**
	 * Uses the specified environment parameter together with other JNDI
	 * properties to initialize this <code>InitialContext</code> object. The
	 * <code>myProps</code> field will be filled with found JNDI properties. If
	 * JNDI standard property "java.naming.factory.initial" has a non-null
	 * value, then <code>getDefaultInitCtx</code> is invoked to try to
	 * initialize fields <code>gotDefault</code> and <code>defaultInitCtx</code>
	 * of the <code>InitialContext</code> object.
	 * 
	 * @param env
	 *            the JNDI environment properties supplied to create the context
	 * @throws NamingException
	 *             If naming problems are encountered during initialization of
	 *             these fields.
	 */
	protected void init(Hashtable<?, ?> env) throws NamingException {
		internalInit(env);
	}

	/*
	 * Initializes the default initial context.
	 * 
	 * @throws NamingException If failed to initialize this InitialContext.
	 */
	private void initializeDefaultInitCtx() throws NamingException {
		if (!gotDefault) {
			defaultInitCtx = NamingManager.getInitialContext(myProps);
			if (null == defaultInitCtx) {
				throw new NoInitialContextException(
						"Failed to create an initial context."); //$NON-NLS-1$
			}
			gotDefault = true;
		}
	}

	/**
	 * Does private initialization.
	 * 
	 * @param env
	 *            the JNDI environment properties used to create the context
	 * @throws NamingException
	 *             If failed to create an InitialContext.
	 */
	@SuppressWarnings("unchecked")
	private void internalInit(Hashtable<?, ?> env) throws NamingException {

		// 1. Read the environment parameter used to create this Context
		if (null == env) {
			myProps = new Hashtable<Object, Object>();
		} else {
			myProps = (Hashtable<Object, Object>) env.clone();
		}

		// 2. Read Applet parameters
		/*
		 * EnvironmentReader.readAppletParameters(myProps.get(Context.APPLET),
		 * myProps);
		 */

		// 3. Read System properties
		EnvironmentReader.readSystemProperties(myProps);

		// 4.1 Read application/applet resource files
		final ClassLoader cl = Thread.currentThread().getContextClassLoader();
		if (propsCache.containsKey(cl)) {
			EnvironmentReader.mergeEnvironment(propsCache.get(cl), myProps,
					true);
		} else {
			final Hashtable<Object, Object> appProps = new Hashtable<Object, Object>();
			EnvironmentReader.readApplicationResourceFiles(appProps);
			propsCache.put(cl, appProps);
			EnvironmentReader.mergeEnvironment(appProps, myProps, true);
		}

		// 4.2 Read "java.home"/lib/jndi.properties
		if (libProperties == null) {
			final Hashtable<Object, Object> props = new Hashtable<Object, Object>();
			EnvironmentReader.readLibraryResourceFile(props);
			libProperties = props;
		}

		EnvironmentReader.mergeEnvironment(libProperties, myProps, true);

		// 5. No need to read service provider resource files

		// if JNDI standard property "java.naming.factory.initial" has a
		// non-null value
		if (myProps.containsKey(INITIAL_CONTEXT_FACTORY)) {
			// call getDefaultInitCtx() to initialize gotDefault and
			// defaultInitCtx
			getDefaultInitCtx();
		}

	}

	@Override
	public NamingEnumeration<NameClassPair> list(Name name)
			throws NamingException {
		return getURLOrDefaultInitCtx(name).list(name);
	}

	@Override
	public NamingEnumeration<NameClassPair> list(String name)
			throws NamingException {
		return getURLOrDefaultInitCtx(name).list(name);
	}

	@Override
	public NamingEnumeration<Binding> listBindings(Name name)
			throws NamingException {
		return getURLOrDefaultInitCtx(name).listBindings(name);
	}

	@Override
	public NamingEnumeration<Binding> listBindings(String name)
			throws NamingException {
		return getURLOrDefaultInitCtx(name).listBindings(name);
	}

	@Override
	public Object lookup(Name name) throws NamingException {
		return getURLOrDefaultInitCtx(name).lookup(name);
	}

	@Override
	public Object lookup(String name) throws NamingException {
		return getURLOrDefaultInitCtx(name).lookup(name);
	}

	@Override
	public Object lookupLink(Name name) throws NamingException {
		return getURLOrDefaultInitCtx(name).lookupLink(name);
	}

	@Override
	public Object lookupLink(String name) throws NamingException {
		return getURLOrDefaultInitCtx(name).lookupLink(name);
	}

	@Override
	public void rebind(Name name, Object obj) throws NamingException {
		getURLOrDefaultInitCtx(name).rebind(name, obj);
	}

	@Override
	public void rebind(String name, Object obj) throws NamingException {
		getURLOrDefaultInitCtx(name).rebind(name, obj);
	}

	@Override
	public Object removeFromEnvironment(String propName) throws NamingException {
		synchronized (contextCache) {
			myProps.remove(propName);
			contextCache.clear();
		}
		return getDefaultInitCtx().removeFromEnvironment(propName);
	}

	@Override
	public void rename(Name oldName, Name newName) throws NamingException {
		getURLOrDefaultInitCtx(oldName).rename(oldName, newName);
	}

	@Override
	public void rename(String oldName, String newName) throws NamingException {
		getURLOrDefaultInitCtx(oldName).rename(oldName, newName);
	}

	@Override
	public void unbind(Name name) throws NamingException {
		getURLOrDefaultInitCtx(name).unbind(name);
	}

	@Override
	public void unbind(String name) throws NamingException {
		getURLOrDefaultInitCtx(name).unbind(name);
	}
}
